package net.lesscoding.buffer;

import lombok.extern.slf4j.Slf4j;
import net.lesscoding.common.Const;
import net.lesscoding.utils.ByteBufferUtil;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;

/**
 * @author eleven
 * @date 2024/10/21 10:41
 * @apiNote
 */
@Slf4j
public class ByteBufferTest {

    /**
     * 文件输入流通道测试
     * <p>注意：</p>
     * <pre>除了文件输入输出流 FileInputStream / FileOutputStream 外，</pre>
     * <pre>还可以使用 随机文件读取 RandomAccessFile来获取 Channel</pre>
     */
    @Test
    public void fileInputStreamChannelTest() {

        // 1. 通过输入流获取Channel
        try (FileInputStream fileInputStream = new FileInputStream(Const.CORE_DATA + "data.txt");
             FileChannel channel = fileInputStream.getChannel()) {
            // 2.准备缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(4);
            // 3. 读取数据到缓冲区
            int read;
            while ((read = channel.read(byteBuffer)) != -1) {
                // 4. 切换读模式
                byteBuffer.flip();
                while (byteBuffer.hasRemaining()) {
                    log.info("当前数据 {}", (char) byteBuffer.get());
                }
                // 5. 清空缓冲区
                byteBuffer.clear();
            }
        } catch (IOException e) {
            log.error("文件输入流通道测试异常", e);
        }
    }

    /**
     * 随机文件读取通道测试
     */
    @Test
    public void randomAccessFileChannelTest() {

    }

    @Test
    public void byteBufferRWTest() {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put((byte) 0X41);
        ByteBufferUtil.debugAll(buffer);
        buffer.put(new byte[]{0x42, 0x43, 0x44, 0x45});
        ByteBufferUtil.debugAll(buffer);
        buffer.flip();
        System.out.println(buffer.get());
        ByteBufferUtil.debugAll(buffer);
        buffer.compact();
        ByteBufferUtil.debugAll(buffer);
    }

    @Test
    public void byteBufferAllocateTest() {
        // class java.nio.HeapByteBuffer java堆内存，读写效率较低
        System.out.println(ByteBuffer.allocate(10).getClass());
        // class java.nio.DirectByteBuffer 直接内存 读写效率较高（少一次拷贝）不受GC的影响，分配时效率低
        System.out.println(ByteBuffer.allocateDirect(10).getClass());
    }

    @Test
    public void byteBufferReadTest() {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J'});
        buffer.flip();

        // 从头开始度
        byte[] bytes = new byte[5];
        buffer.get(bytes);
        System.out.println(new String(bytes));
        ByteBufferUtil.debugAll(buffer);
        // 使用 rewind 将 position 重置为0
        System.out.println((char) buffer.get());
        buffer.rewind();
        System.out.println((char) buffer.get());
        ByteBufferUtil.debugAll(buffer);
        // 使用 mark 标记当前位置   reset 重置到 mark 位置
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        // 标记位置
        buffer.mark();
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        // 重置到标记位置
        buffer.reset();
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        // 获取特定下标的数据
        System.out.println((char) buffer.get(3));
    }

    /**
     * Buffer 与 String 转换
     */
    @Test
    public void ByteBuffer2StringTest() {
        String str = "Hello, World!";

        ByteBuffer buffer1 = ByteBuffer.allocate(16);
        buffer1.put(str.getBytes());
        ByteBufferUtil.debugAll(buffer1);

        // 字符串转Buffer
        ByteBuffer buffer2 = ByteBuffer.wrap(str.getBytes());
        ByteBufferUtil.debugAll(buffer2);

        // 使用标准字符集转换
        ByteBuffer encode = StandardCharsets.UTF_8.encode(str);
        ByteBufferUtil.debugAll(encode);

        // Buffer转字符串
        String decode = StandardCharsets.UTF_8.decode(encode).toString();
        System.out.println(decode);
    }

    /**
     * 分散读取
     */
    @Test
    public void scatteringReadTest() {
        try (FileChannel channel = new RandomAccessFile(Const.CORE_DATA + "data.txt", "rw").getChannel()) {
            ByteBuffer buffer1 = ByteBuffer.allocate(3);
            ByteBuffer buffer2 = ByteBuffer.allocate(4);
            ByteBuffer buffer3 = ByteBuffer.allocate(5);
            channel.read(new ByteBuffer[]{buffer1, buffer2, buffer3});
            buffer1.flip();
            buffer2.flip();
            buffer3.flip();
            ByteBufferUtil.debugAll(buffer1);
            ByteBufferUtil.debugAll(buffer2);
            ByteBufferUtil.debugAll(buffer3);
        } catch (IOException e) {
        }
    }

    /**
     * 聚集写入
     */
    @Test
    public void gatheringQWrite() {
        ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("Hello");
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("World");
        ByteBuffer buffer3 = StandardCharsets.UTF_8.encode("!");

        try (FileChannel channel = new RandomAccessFile(Const.CORE_DATA + "data.txt", "rw").getChannel()) {
            long write = channel.write(new ByteBuffer[]{buffer1, buffer2, buffer3});

        } catch (IOException e) {
            log.error("写入失败", e);
        }
        log.info("写入完成");
    }

    @Test
    public void splitTest() {

        ByteBuffer source = ByteBuffer.allocate(32);
        source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
        split(source);
        source.put("w are you?\n".getBytes());
        split(source);
    }

    /**
     * 将 缓冲区 中 数据 按照 \n 进行分割
     * 处理网络编程中的粘包和半包
     *
     * @param source 来源ByteBuffer
     */
    private static void split(ByteBuffer source) {
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            if (source.get(i) == '\n') {
                int length = i + 1 - source.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
                ByteBufferUtil.debugAll(target);
            }
        }
        source.compact();
    }
}
